Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improvement: ignore create or modify file watcher event when content did not change #7006

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

kasiaMarek
Copy link
Contributor

@kasiaMarek kasiaMarek commented Dec 5, 2024

fixes: #6896

Problem

Code generation leads to infinite compilation loop.

Solution

Ignore create or modify file watcher event when content of the file is the same as with the previously when this event was triggered. Ignore compile for file if it did not change from last compile.

event.eventType match {
case EventType.CreateOrModify =>
try {
val md5 = MD5.bytesToHex(FileIO.readAllBytes(path))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Later in onChange the file is read again (if the event isn't ignored). Should I try to deduplicate these reads?

event.eventType match {
case EventType.CreateOrModify =>
try {
val md5 = MD5.bytesToHex(FileIO.readAllBytes(path))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should do it before compile? So that we wouldn't do noop compilations as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put it before compile. I also added a draft of a solution to deduplicate reading in file content. (I feel like that might be costly.) WDYT?

Copy link
Contributor Author

@kasiaMarek kasiaMarek Dec 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realised we also compile the file when it did not change but its dependencies did (on didFocus). I wonder if it's safe to always discard compile on didSave if the file did not change. Can we rely only on didFocus? Otherwise, there may be some compilation that finished and user may want to force compile (on this build target) using save. Maybe the first option to only deduplicate on file watcher events was better?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realised we also compile the file when it did not change but its dependencies did (on didFocus). I wonder if it's safe to always discard compile on didSave if the file did not change.

But if they want to force a save with didSave, they need to change the file, right? Did save will not be sent on a unchanged file anyway.

If any upstream projects change, we should always maybe mark anything that depends on them as "dirty" and should always be recompiled next time a compilation is invoked?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if they want to force a save with didSave, they need to change the file, right? Did save will not be sent on a unchanged file anyway

At least in VSCode Save will always send didSave even if the file did not change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see didSave being sent if I save an unchanged file 🤔 Or do you mean in a different case?

focusedDocumentBuildTarget: Option[BuildTargetIdentifier],
): Future[Unit] = {
val paths =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could just find first that changed in a given target?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, though I doubt it ever too many files at once. Only file watcher passes more than one file.

import scala.meta.io.AbsolutePath

class SavedFileSignatures {
private val previousCreateOrModify = TrieMap[AbsolutePath, String]()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we reuse MutableMd5Fingerprints somehow? Maybe just save them in didSave with a flag to MutableMd5Fingerprints and then we can get the last saved one instead of calculating again?

We can still have a separate collection in Compilations so that it's possible to clear it and force new compilations?

Copy link
Contributor Author

@kasiaMarek kasiaMarek Dec 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For didSave we put them anyway there but for file watcher event like CreateOrModify we don't, and we don't want to since it stores content, which we don't need. Also there is no way to check that the caller of compileFile/s saved the fingerprint (there may be an old one there), so it becomes some hidden knowledge that you should.
Edit: we can potentially use flags and mark "used ones"

@kasiaMarek kasiaMarek requested a review from tgodzik December 11, 2024 13:53
@kasiaMarek kasiaMarek marked this pull request as ready for review December 11, 2024 13:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Compilation triggered repeatedly with no results using SBT BSP
2 participants